#!/usr/bin/env python

"""
This class uses the decorator pattern to cache functions/methods call.
Parameters:
 - timeout: the time in seconds that the key/value should be refreshed
            Defaults to none (ie. cached indefinitely)
 - key: the name of the key, it can be a string used in str.format to
        dynamically change the key name based of function/method parameters
	Defaults to md5 of args/kwargs of the function/method

Example:

@cache ( timeout = 60 )
def long_op ( liwe, ... ):
	...

class My_Class ( LiweModule ):
	@cache ( timeout = 60, key = 'cache-key' )
	def long_method ( self, ... ):
		...

"""

import hashlib, sys
from functools import wraps
from os3.utils.cache_man import CacheHandler

from module import LiweModule
from liwe import liwe

class cache ( object ):

	def __init__ ( self, timeout = None, key = None, skips = None, prefix = None ):
		self.timeout = timeout
		self.key = key
		self.prefix = prefix
		if skips is None:
			skips = lambda a,b: False
		self.skips = skips

		self.handler = None 

	def __get_key ( self, args, kwargs ):
		s = args + ( '#', ) + tuple ( sorted ( kwargs.items () ) )
		return self.prefix + hashlib.md5 ( str ( s ) ).hexdigest ()
	
	def __get_args ( self, fn, args, kwargs ):
		import inspect
		args_names, _, _, defaults = inspect.getargspec ( fn )
		passed_args = {}

		if defaults is not None:
			passed_args = dict ( zip ( args_names [ - len ( defaults ) : ], defaults ) )

		passed_args.update ( dict ( zip ( args_names, args ) ) )
		passed_args.update ( kwargs )

		passed_varargs = args [ len ( args_names ) : ]

		return passed_args, passed_varargs

	def __call__ ( self, fn ):
		@wraps ( fn )
		def wrapper ( obj, * args, ** kwargs ):

			result = None
			skips = self.skips ( args, kwargs )

			if not skips:
				l = None
				if isinstance ( obj, LiweModule ):
					l = obj.liwe
					self.__init_handler ( l, obj )
				elif isinstance ( obj, liwe ):
					l = obj
					self.__init_handler ( l )


				key = None
				if self.key is None:
					key = self.__get_key ( args, kwargs )
				else:
					args_values, _ = self.__get_args ( fn, args, kwargs )
					key = self.key.format ( ** args_values )

				if self.handler:
					result = self.handler.get ( key )

			if result is None:
				result = fn ( obj, * args, ** kwargs )

			if not skips:
				self.handler.set ( key, result, self.timeout )

			return result
		return wrapper

	def __init_handler ( self, l, module = None ):
		if self.handler: return

		res = l.cfg.get ( "site.cache" )
		if module:
			res = module.cfg.get ( "cache", res )

		if not res:
			l.pyhp.log.error ( "ERROR: no cache config defined in liwe.yaml" )
			return

		self.handler = CacheHandler.create ( res [ 'mode' ], res.get ( "params", {} ) )
		

if __name__ == '__main__':

	import pylibmc
	memc = pylibmc.Client ( [ '127.0.0.1' ] )

	class FakeLiwe:
		pass

	liwe = FakeLiwe ()
	liwe.memc = memc

	@cache ( timeout = 60, key = 'func-key' )
	def func ( liwe, mode = 'test' ):
		return [ 'test' ]

	class Test ( LiweModule ):
		def __init__ ( self, liwe ):
			self.liwe = liwe
			self.value = 10
		@cache ( timeout = 30, key = 'test-{mode}', skips = lambda args, kwargs: kwargs.get ( 'mode' ) == 'no-cache' )
		def meth ( self, text, mode = 'list', limit = 10 ):
			return [ 'meth-test: %s (value=%s)' % ( text, self.value ) ]

	print func ( liwe )
	print func ( liwe, 'list' )
	t = Test ( liwe )
	print t.meth ( "default" )
	print t.meth ( "mode=count", mode = 'count' )
	print t.meth ( "mode=no-cache", mode = 'no-cache' )
